package tests.experiments.connery;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jeconbond.Q.computation.IProgressFunc;
import jeconbond.Q.computation.ProdPossProgressFunc;
import jeconbond.automata.INode;
import jeconbond.automata.IOutcome;
import jeconbond.automata.Node;
import jeconbond.automata.Outcome;
import jeconbond.automata.behaviour.NaturalAuditWalkStrategy;
import jeconbond.economic.agent.IEconomicAgent;
import jeconbond.economic.resources.ResourceBasket;
import jeconbond.economic.resources.ResourceStorrage;
import jeconbond.economic.resources.IResourceType;
import jeconbond.economic.resources.ResourceType;
import jeconbond.economic.systems.natural.NaturalRRSFactory;
import jeconbond.experiment.natural.prodpossibfrontier.ConneryExperimentSettings;
import jeconbond.experiment.natural.prodpossibfrontier.generate.AdaptiveProdPPFGraphGenerator;
import jeconbond.experiment.natural.prodpossibfrontier.generate.SetAuditStrategyBasedAgentsCreator;
import jeconbond.experiment.natural.prodpossibfrontier.process.ConneryExperimentContext;
import jeconbond.experiment.JEBExperimentFlow;
import junit.framework.Assert;
import junit.framework.TestCase;
import laboratoryQ.flatness.Point;

public class TestAuditWalker extends TestCase {
	public void testFindBestResult() {
		IResourceType tankResource = new ResourceType("Tank");
		IResourceType traktorResource = new ResourceType("Traktor");
		
		ResourceStorrage inputRS = new ResourceStorrage(); 
		ResourceStorrage outputRS = new ResourceStorrage();
		INode root = new Node("root");
		
		IOutcome out = null;
		
		INode n1 = new Node("n1");
		out = new Outcome(n1);
		out.setProducedResources(new ResourceBasket(tankResource, 1.0));
		root.getOutcomes().add(out);
		INode n11 = new Node("n11");
		out = new Outcome(n11);
		out.setProducedResources(new ResourceBasket(traktorResource, 2.0));
		n1.getOutcomes().add(out);
		out = new Outcome(root);
		out.setProducedResources(
				new ResourceBasket(tankResource, 1.0),
				new ResourceBasket(traktorResource, 2.0)
		);
		n11.getOutcomes().add(out);
		
		INode n2 = new Node("n2");
		out = new Outcome(n2);
		out.setProducedResources(
				new ResourceBasket(tankResource, 1.0),
				new ResourceBasket(traktorResource, 1.0)
		);
		root.getOutcomes().add(out);
		INode n21 = new Node("n21");
		out = new Outcome(n21);
		out.setProducedResources(
				new ResourceBasket(tankResource, 1.0),
				new ResourceBasket(traktorResource, 1.0)
		);
		n2.getOutcomes().add(out);
		INode n22 = new Node("n22");
		out = new Outcome(n22);
		out.setProducedResources(
				new ResourceBasket(tankResource, 2.0)
		);
		n2.getOutcomes().add(out);
		n21.getOutcomes().add(new Outcome(root));
		n22.getOutcomes().add(new Outcome(root));
		
		NaturalAuditWalkStrategy aws = new NaturalAuditWalkStrategy(
				inputRS, 
				outputRS,
				root,
				new double[] {1.0},
				new int[] {0}, 
				tankResource, 
				traktorResource
		); 
		List<IOutcome> plan = new ArrayList<IOutcome>();
		NaturalAuditWalkStrategy.FindBestResultContext fbrc = null;
		
		fbrc = aws.new FindBestResultContext(plan, 0.5);
		fbrc.find();
		Assert.assertEquals("n1", plan.get(0).getDestination().getName());
		
		fbrc = aws.new FindBestResultContext(plan, 1.0);
		fbrc.find();
		Assert.assertEquals("n2", plan.get(0).getDestination().getName());
		Assert.assertEquals("n21", plan.get(1).getDestination().getName());
		
		fbrc = aws.new FindBestResultContext(plan, 3.0);
		fbrc.find();
		Assert.assertEquals("n2", plan.get(0).getDestination().getName());
		Assert.assertEquals("n22", plan.get(1).getDestination().getName());
		Assert.assertEquals("root", plan.get(2).getDestination().getName());
	}
	
	public void testAuditWalker() {
		IResourceType tankResource = new ResourceType("Tank");
		IResourceType traktorResource = new ResourceType("Traktor");
		
		ResourceStorrage inputRS = new ResourceStorrage(); 
		ResourceStorrage outputRS = new ResourceStorrage();
		INode root = new Node("root");
		
		IOutcome out = null;
		
		INode n1 = new Node("n1");
		out = new Outcome(n1);
		out.setProducedResources(new ResourceBasket(tankResource, 1.0));
		root.getOutcomes().add(out);
		INode n11 = new Node("n11");
		out = new Outcome(n11);
		out.setProducedResources(new ResourceBasket(traktorResource, 2.0));
		n1.getOutcomes().add(out);
		out = new Outcome(root);
		out.setProducedResources(
				new ResourceBasket(tankResource, 1.0),
				new ResourceBasket(traktorResource, 2.0)
		);
		n11.getOutcomes().add(out);
		
		INode n2 = new Node("n2");
		out = new Outcome(n2);
		out.setProducedResources(
				new ResourceBasket(tankResource, 1.0),
				new ResourceBasket(traktorResource, 1.0)
		);
		root.getOutcomes().add(out);
		INode n21 = new Node("n21");
		out = new Outcome(n21);
		out.setProducedResources(
				new ResourceBasket(tankResource, 1.0),
				new ResourceBasket(traktorResource, 1.0)
		);
		n2.getOutcomes().add(out);
		INode n22 = new Node("n22");
		out = new Outcome(n22);
		out.setProducedResources(
				new ResourceBasket(tankResource, 2.0)
		);
		n2.getOutcomes().add(out);
		n21.getOutcomes().add(new Outcome(root));
		n22.getOutcomes().add(new Outcome(root));
		
		
		NaturalAuditWalkStrategy aws = new NaturalAuditWalkStrategy(
				inputRS, 
				outputRS,
				root,
				new double[] {1.0},
				new int[] {0}, 
				traktorResource, 
				traktorResource
		);
		INode state = root;
		Map<String, String> stateNames = new HashMap<String, String>();
		for (int i = 0; i < 100; i++) {
			stateNames.put(state.getName(), state.getName());
			state = aws.doWalk(state).getDestination();
		}
		Assert.assertTrue(stateNames.size() > 2);
	}
	
	public void test0relation() {
		IResourceType tankResource = new ResourceType("Tank");
		IResourceType traktorResource = new ResourceType("Traktor");
		
		ResourceStorrage inputRS = new ResourceStorrage(); 
		ResourceStorrage outputRS = new ResourceStorrage();
		INode root = new Node("root");
		root.getOutcomes().add(null);
		
		INode n1 = new Node("n1");
		IOutcome out1 = new Outcome(n1);
		out1.setProducedResources(new ResourceBasket(tankResource, 1.0));
		n1.getOutcomes().add(new Outcome(root));
		
		INode n2 = new Node("n2");
		IOutcome out2 = new Outcome(n2);
		out2.setProducedResources(new ResourceBasket(traktorResource, 1.0));
		n2.getOutcomes().add(new Outcome(root));

		root.getOutcomes().set(0, out1);
		{
			NaturalAuditWalkStrategy aws = new NaturalAuditWalkStrategy(
					inputRS, 
					outputRS,
					root,
					new double[] {1.0},
					new int[] {0}, 
					traktorResource, 
					traktorResource
			);
			INode state = root;
			Map<String, String> stateNames = new HashMap<String, String>();
			for (int i = 0; i < 100; i++) {
				stateNames.put(state.getName(), state.getName());
				state = aws.doWalk(state).getDestination();
			}
			Assert.assertTrue(stateNames.size() > 1);
		}
		root.getOutcomes().set(0, out2);
		{
			NaturalAuditWalkStrategy aws = new NaturalAuditWalkStrategy(
					inputRS, 
					outputRS,
					root,
					new double[] {1.0},
					new int[] {0}, 
					traktorResource, 
					traktorResource
			);
			INode state = root;
			Map<String, String> stateNames = new HashMap<String, String>();
			for (int i = 0; i < 100; i++) {
				stateNames.put(state.getName(), state.getName());
				state = aws.doWalk(state).getDestination();
			}
			Assert.assertTrue(stateNames.size() > 1);
		}
		root.getOutcomes().add(out1);
		{
			NaturalAuditWalkStrategy aws = new NaturalAuditWalkStrategy(
					inputRS, 
					outputRS,
					root,
					new double[] {1.0},
					new int[] {0}, 
					traktorResource, 
					traktorResource
			);
			INode state = root;
			Map<String, String> stateNames = new HashMap<String, String>();
			for (int i = 0; i < 100; i++) {
				stateNames.put(state.getName(), state.getName());
				state = aws.doWalk(state).getDestination();
			}
			Assert.assertTrue(stateNames.size() > 1);
		}
	}

	public void testGeneratorInDeal() {
		int index = 30;
		int total = 100;

		ConneryExperimentSettings settings = new ConneryExperimentSettings();
		settings.initialise();
		settings.agentsCount = total;
		settings.stepSize = -2;
		settings.rrsFactory = new NaturalRRSFactory();
		settings.ironResource = new ResourceType("Iron");
		settings.tankResource = new ResourceType("Tank");
		settings.traktorResource = new ResourceType("Traktor");
		settings.resources = new IResourceType[] { settings.ironResource, settings.tankResource, settings.traktorResource };
		settings.stepCount = 10;
		settings.branchesCount = 11;
//		settings.branchLength = 3;
		settings.relationBoard = 5;
		settings.tankMul = 1.0;
		settings.traktorMul = 3.0;
		settings.relationDistPercent = 0.1;
		settings.fluctuationPart = 0.0;
		settings.agentPerStep = total;
		settings.experiment = new JEBExperimentFlow();
		settings.goalsArray = new double[0];
		settings.chainLength = 10;
		settings.zeroPoint = new Point(0, 0);
		settings.randomSeed = 123;
		settings.ironByStep = -2;
		settings.validate();
		ConneryExperimentContext context = new ConneryExperimentContext(settings);
		AdaptiveProdPPFGraphGenerator generator = new AdaptiveProdPPFGraphGenerator();
		INode[] allAutomatas = generator.generate(context);
		INode root = allAutomatas[index];

		IProgressFunc progressFunc = new ProdPossProgressFunc(settings.tankMul, settings.traktorMul);
		
		double[] rels = extractRels(root, settings.tankResource);
		double relCent = (index + settings.relationBoard) / (double)(total + settings.relationBoard);
		double[] out = new double[2];
		progressFunc.compute(relCent, out);
		relCent = out[0] / out[1];
		int greater = 0;
		for (double cr : rels) {
			greater += cr > relCent ? 1 : 0;
		}
		Assert.assertEquals(settings.branchesCount / 2, greater);

		SetAuditStrategyBasedAgentsCreator setAuditAgentsCreator =
				new SetAuditStrategyBasedAgentsCreator();

		IEconomicAgent agent = setAuditAgentsCreator.automata2agents(allAutomatas, context)[index];

		settings.goalsArray[0] = rels[1];
		settings.goalsArray[1] = rels[settings.branchesCount - 2];

		context.getRRS().storeResource(settings.ironResource, 1000.0);
		while (true) {
			if (context.getRRS().howMuchWasStored(settings.tankResource) > 0.0) {
				break;
			}
			agent.processPreProduceStage();
			agent.processProduceStage();
			agent.processPostProduceStage();
		}
		double rel1 =
			context.getRRS().howMuchWasStored(settings.tankResource) /
			context.getRRS().howMuchWasStored(settings.traktorResource);
		Assert.assertTrue(Math.abs(rel1 - settings.goalsArray[0]) < 1e-5);
		context.getRRS().receiveResource(settings.tankResource, Double.MAX_VALUE);
		context.getRRS().receiveResource(settings.traktorResource, Double.MAX_VALUE);
		context.getNextStepCommander().stepCounter[0]++;
		while (true) {
			if (context.getRRS().howMuchWasStored(settings.tankResource) > 0.0) {
				break;
			}
			agent.processPreProduceStage();
			agent.processProduceStage();
			agent.processPostProduceStage();
		}
		double rel2 =
			context.getRRS().howMuchWasStored(settings.tankResource) /
			context.getRRS().howMuchWasStored(settings.traktorResource);
		Assert.assertTrue(Math.abs(rel2 - settings.goalsArray[1]) < 1e-5);

		Assert.assertTrue(Math.abs(rel1 - settings.goalsArray[0]) < Math.abs(rel2 - settings.goalsArray[0]));
		Assert.assertTrue(Math.abs(rel1 - settings.goalsArray[1]) > Math.abs(rel2 - settings.goalsArray[1]));
	}

	private static double[] extractRels(INode root, IResourceType tankResource) {
		int n = root.getOutcomes().size();
		double[] result = new double[n];
		for (int i = 0; i < n; i++) {
			INode d = root.getOutcomes().get(i).getDestination();
			while (true) {
				INode c = d.getOutcomes().get(0).getDestination();
				if (c == root) {
					break;
				}
				d = c;
			}
			double tank = 0.0;
			double traktor = 0.0;
			for (ResourceBasket rb : d.getOutcomes().get(0).getProducedResources()) {
				if (rb.type == tankResource) {
					tank = rb.value;
				} else {
					traktor = rb.value;
				}
			}
			result[i] = tank / traktor;
		}
		return result;
	}
}
